﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using API;
using Foundation;
using ILRuntime.Mono.Cecil;
using ILRuntime.Mono.Cecil.Cil;
using MyExtensions;
using Newtonsoft.Json.Linq;
using Scriban;
using Scriban.Runtime;
using UIKit;
using Xamarin.Forms;

namespace mb.iOS
{
    [Register("AppDelegateGenBind")]
    public class AppDelegateGenBind : UIApplicationDelegate
    {
        static string MainDLLPath;
        static string HotDLLPath;

        /*
        过滤器如有选项用/分隔，其后每个参数用;分隔。
        目前支持的选项有：
        ncb: (not callbase)不要调用基类方法。某些xamarin基类方法会强制不让调用，但并没有统一规则判断，只能手工标注之。
         */
        class AdaptorInfo
        {
            public Type type;
            public string[] filter;
        };
        static AdaptorInfo[] ExtraAdaptorList = new AdaptorInfo[]
        {
            new AdaptorInfo { type = typeof(IDisposable), filter = new string[] { ".*" } },
            new AdaptorInfo { type = typeof(BaseViewModel), filter = new string[] { "^X" } },
            new AdaptorInfo { type = typeof(System.Runtime.CompilerServices.IAsyncStateMachine), filter = new [] { ".*", } },
            ////////////////// 
            new AdaptorInfo { type = typeof(EventArgs), filter = new [] { ".*" } },
            new AdaptorInfo { type = typeof(AVKit.AVPlayerViewControllerDelegate), filter = new [] { "^Did", "^Should", "^Will" } },

            new AdaptorInfo { type = typeof(UIScrollView), filter = new string[] { } },
            new AdaptorInfo { type = typeof(UIView), filter = new string[] { "^Will", "get_IsAccessibilityElement", "IntrinsicContentSize" } },

            new AdaptorInfo
            {
            type = typeof(UIViewController), filter = new []
            {
            "^View",
            "UpdateViewConstraints",
            }
            },

            new AdaptorInfo { type = typeof(UINavigationController), filter = new string[] { } },
            new AdaptorInfo { type = typeof(UITabBarController), filter = new string[] { "^View", } },
            new AdaptorInfo { type = typeof(UITableViewSource), filter = new string[] { "^GetCell", "^Row/ncb" } },
            new AdaptorInfo
            {
            type = typeof(UICollectionViewController), filter = new string[]
            {
            "^View/ncb",
            "^Should/ncb",
            "^Will/ncb",
            "^Item",
            "^Can",
            "^Did(?!Change)"
            }
            },

            new AdaptorInfo
            {
            type = typeof(UICollectionViewSource), filter = new string[]
            {
            "^Can",
            "^Did",
            "^GetCell",
            "^Item",
            "NumberOfSections",
            "GetItemsCount",
            }
            },

            new AdaptorInfo { type = typeof(UICollectionViewLayout), filter = new string[] { "Prepare" } },
            new AdaptorInfo { type = typeof(UICollectionViewCell), filter = new string[] { } },

            new AdaptorInfo
            {
            type = typeof(UICollectionViewDelegateFlowLayout), filter = new string[]
            {
            "Get.*For.*/ncb",
            }
            },

            new AdaptorInfo
            {
            type = typeof(UIGestureRecognizerDelegate), filter = new string[]
            {
            "Should/ncb",
            }
            },

            new AdaptorInfo
            {
            type = typeof(ContentPage), filter = new string[]
            {
            "Should/ncb",
            }
            },
        };

        string ReportMessage;

        public override UIWindow Window
        {
            get;
            set;
        }

        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {

            GenCode();
            CreateUI();
            return true;
        }

        void CreateUI()
        {
            Window = new UIWindow(UIScreen.MainScreen.Bounds);
            Window.RootViewController = new UIViewController();
            Window.BackgroundColor = new UIColor(1.0f, .0f, 1.0f, 0.5f);
            Window.MakeKeyAndVisible();

            string[] pargs = NSProcessInfo.ProcessInfo.Arguments.ToArray<string>();
            var tv = new UITextView();
            tv.Text = string.Join("\n", pargs.Concat(new [] { ReportMessage }));
            tv.Frame = Window.GetFrame();
            tv.BackgroundColor = new UIColor(1, 1, 1, 1);
            tv.Editable = false;
            tv.Font = UIFont.SystemFontOfSize(20);

            Window.RootViewController.View.AddSubview(tv);
        }

        void GenCode()
        {
            ServiceLocator.SilentError = true;
            var so = ServiceLocator.Instance.Get<IStartOption>();

            MainDLLPath = so.GetStringValue("mainDllPath");
            HotDLLPath = so.GetStringValue("hotDllPath");

            var tplRootPath = so.GetStringValue("tplRootPath");
            var genRootPath = so.GetStringValue("genRootPath");

            System.Console.WriteLine("** start to gen ilr");
            var interfaceTemplatePath = $"{tplRootPath}/interface.sbntxt";
            var delegateTemplatePath = $"{tplRootPath}/delegate.sbntxt";

            var genPath1 = $"{genRootPath}/ilrbind/adaptor_new/";
            var genPath2 = $"{genRootPath}/ilrbind/typebind_new/";
            var genPath3 = $"{genRootPath}/ilrbind/delegate_new/";

            Action<string> FinishAction = p =>
            {
                var old = p.Replace("_new", "");
                if (Directory.Exists(old)) Directory.Delete(old, true);
                Directory.Move(p, old);
            };

            GenerateAdaptor(interfaceTemplatePath, genPath1);

            if (Directory.GetFiles(genPath1).Length > Directory.GetFiles(genPath1.Replace("_new", "")).Length)
            {
                ReportMessage = "已生成新的adaptor，请重编引擎再运行绑定生成任务";
                FinishAction(genPath1);
                return;
            }

            GenerateCLRBindingCode(genPath2);

            //扫描逻辑不太稳定，还是手工填写先
            // GenerateCLRDelegateRegisterCode(delegateTemplatePath, genPath3);

            ReportMessage = "ilr生成成功";

            //全部生成成功，才替换旧文件
            new []
            {
                genPath1,
                genPath2,
                // genPath3
            }.ToList().ForEach(FinishAction);
        }

        void GenerateCLRBindingCode(string outputPath)
        {
            if (System.IO.Directory.Exists(outputPath))
                System.IO.Directory.Delete(outputPath, true);

            //分析热更dll调用引用来生成绑定代码
            ILRuntime.Runtime.Enviorment.AppDomain domain = new ILRuntime.Runtime.Enviorment.AppDomain();
            try
            {
                domain.LoadAssembly(new System.IO.FileStream(HotDLLPath, System.IO.FileMode.Open, System.IO.FileAccess.Read));
            }
            catch (Exception e)
            {
                Console.WriteLine("-> load hotdll failed,{0},{1}", HotDLLPath, e);
                return;

            }

            //这里需要注册所有热更DLL中用到的跨域继承Adapter，否则无法正确抓取引用
            ILRuntime.Runtime.Generated.CLRAdaptorRegister.Initialize(domain);

            Console.WriteLine("-> begin gen bind");
            try
            {
                ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(domain, outputPath);
                Console.WriteLine("** succeed to gen bind");
            }
            catch (Exception e)
            {
                Console.WriteLine("** failed to gen bind");
                Console.WriteLine(e);
                System.Environment.Exit(1);
            }
        }

        public void GenerateAdaptor(string templatePath, string outputPath)
        {
            var template = Template.Parse(System.IO.File.ReadAllText(templatePath));

            if (System.IO.Directory.Exists(outputPath))
                System.IO.Directory.Delete(outputPath, true);

            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            int typeCount = 0;
            int adaptorCount = 0;
            for (int i = 0; i < assemblies.Length; i++)
            {
                var assembly = assemblies[i];
                foreach (Type type in assembly.GetTypes())
                {
                    typeCount++;
                    if (type.GetCustomAttributes(typeof(ILRuntime.Other.NeedAdaptorAttribute), true).Length > 0)
                    {
                        // logger.Log("type", type);
                        if (type.IsInterface)
                        {
                            adaptorCount++;
                            GenerateAdaptor(type, template, outputPath + "adaptor_" + type.Name + ".cs");
                        }
                    }
                }
            }

            ExtraAdaptorList.ToList().ForEach(v =>
            {
                GenerateAdaptor(v.type, template, outputPath + $"adaptor_{v.type.Name}.cs", v.filter);
            });

            Console.WriteLine("** succeed to gen adaptor");
        }

        void GenerateAdaptor(Type type, Template template, string outputPath, string[] methodFilter = null)
        {
            var so = new ScriptObject();
            var context = new TemplateContext();
            context.PushGlobal(so);
            so.Add("className", type.Name);
            so.Add("classFullName", type.FullName);
            so.Add("type", type);
            var methods = new List<MethodInfo>();
            var methodFlags = new Dictionary<MethodInfo, Dictionary<string, string>>();

            Func<string, string> fixReturnTypeName = (string name) =>
            {
                if (name == "System.Void") return "void";
                return name;
            };

            Func<MethodInfo, bool, string> getMethodParamSig = (MethodInfo m, bool withType) =>
            {
                var param = m.GetParameters();
                if (param.Length == 0) return "()";
                string ret = "";
                foreach (var p in ForEachHelper.WithIndex(param))
                {
                    var tn = p.Value.ParameterType.FullName;
                    // var isRef = p.Value.IsOut;
                    var isRef = tn.Last() == '&';
                    //如果以&结尾，转成ref
                    if (isRef) tn = "ref " + tn.Remove(tn.Length - 1);
                    if (withType)
                    {
                        ret += $"{tn} p{p.Index}";
                    }
                    else
                    {
                        var s = $"p{p.Index}";
                        if (isRef) s = "ref " + s;

                        ret += s;
                    }

                    if (!p.IsLast)
                        ret += ", ";
                }
                return "(" + ret + ")";
            };

            if (type.IsInterface)
            {
                methods.AddRange(type.GetMethods());
                // logger.Log("get self methods", methods.Count);
                const BindingFlags flags = BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance;
                foreach (Type interf in type.GetInterfaces())
                {
                    System.Console.WriteLine("get interface:" + interf);
                    foreach (MethodInfo method in interf.GetMethods(flags))
                    {
                        // System.Console.WriteLine("get method:" + method);
                        if (!method.Name.StartsWith("get_"))
                            methods.Add(method);
                    }
                }
            }
            else
            {
                System.Console.WriteLine("get class:" + type);
                var accessorMap = new Dictionary<string, MethodInfo>();

                const BindingFlags flags = BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance;
                foreach (MethodInfo method in type.GetMethods(flags))
                {
                    var passTest = false;
                    Dictionary<string, string> mflags = null;

                    //这是一个属性
                    // if (method.IsSpecialName) continue;

                    foreach (var mf in methodFilter)
                    {
                        var mfl = mf.Split("/");
                        var re = new Regex(mfl[0]);
                        if (re.Match(method.Name).Success)
                        {
                            passTest = true;
                            if (mfl.Length >= 2)
                            {
                                mflags = mfl[1].Split(";").ToDictionary(v =>
                                {
                                    return v;
                                });
                                System.Console.WriteLine("process flags:" + mfl[1]);
                            }
                            break;
                        }
                    }
                    if (passTest)
                    {
                        System.Console.WriteLine("get method:" + method);

                        bool is_get = method.IsSpecialName && method.Name.StartsWith("get_");
                        bool is_set = method.IsSpecialName && method.Name.StartsWith("set_");
                        if (is_get || is_set)
                        {
                            var pname = method.Name.Substring(4);
                            MethodInfo otherMethod;
                            //如果另一个已经存在，给它添上flag就行，最终只需要导出一个方法，因为get/set是写在一起的
                            if (accessorMap.TryGetValue(pname, out otherMethod))
                            {
                                mflags = methodFlags[otherMethod];
                                if (is_get)
                                    mflags.Add("accessor_get", "1");
                                if (is_set)
                                    mflags.Add("accessor_set", "1");
                            }
                            else
                            {
                                mflags = new Dictionary<string, string>();
                                mflags.Add("accessor", "1");

                                if (is_get)
                                    mflags.Add("accessor_get", "1");
                                if (is_set)
                                    mflags.Add("accessor_set", "1");

                                methodFlags[method] = mflags;
                                accessorMap[pname] = method;
                                methods.Add(method);
                            }
                        }
                        else
                        {
                            if (mflags != null)
                                methodFlags[method] = mflags;

                            methods.Add(method);
                        }

                    }
                }

            }

            // logger.Log("get all methods", methods.Count);
            Dictionary<string, string> emptyFlag = new Dictionary<string, string>();
            var ms = new List<ScriptObject>();
            int id = 0;
            foreach (var mi in methods)
            {
                var mflags = methodFlags.ContainsKey(mi) ? methodFlags[mi] : emptyFlag;
                var isAccessor = mflags.ContainsKey("accessor");
                var m = new ScriptObject();
                ms.Add(m);
                m.Add("id", id++);
                m.Add("name", isAccessor?mi.Name.Substring("get_".Length) : mi.Name);
                m.Add("accessor", isAccessor);
                m.Add("accessor_get", mflags.ContainsKey("accessor_get"));
                m.Add("accessor_set", mflags.ContainsKey("accessor_set"));
                m.Add("override", (type.IsClass && mi.IsVirtual) ? "override" : "");
                m.Add("callBase", !type.IsInterface && !mi.IsAbstract && !mflags.ContainsKey("ncb"));
                m.Add("returnTypeName", fixReturnTypeName(mi.ReturnType.FullName));
                m.Add("paramSig", isAccessor? "": getMethodParamSig(mi, true));
                m.Add("paramList", isAccessor? "": getMethodParamSig(mi, false));
                m.Add("paramCount", mi.GetParameters().Length);

            }
            so.Add("methods", ms);

            var result = template.Render(context);
            System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath));
            System.IO.File.WriteAllText(outputPath, result);
        }

        private class CustomResolver : DefaultAssemblyResolver
        {
            public override AssemblyDefinition Resolve(AssemblyNameReference name)
            {
                ILog logger = ServiceLocator.Instance.Get<ILog>();
                logger.Log("request resolve", name);
                return name.Name == "Assembly-CSharp" ?
                    AssemblyDefinition.ReadAssembly(MainDLLPath) :
                    base.Resolve(name);
            }
        }
        void GenerateCLRDelegateRegisterCode(string templatePath, string genPath)
        {
            ILog logger = ServiceLocator.Instance.Get<ILog>();

            Dictionary<string, string> methodDelegates = new Dictionary<string, string>();
            Dictionary<string, string> functionDelegates = new Dictionary<string, string>();
            Dictionary<string, JObject> convertorDelegates = new Dictionary<string, JObject>();

            ReaderParameters rp = new ReaderParameters { AssemblyResolver = new CustomResolver() };

            var hotModule = ModuleDefinition.ReadModule(HotDLLPath, rp);
            var ownedTypes = hotModule.Types.ToDictionary((v) => v.FullName);
            ownedTypes.ToList().ForEach(v =>
            {
                // logger.Log("hotdll type", v, v.Value.NestedTypes.Count);
                //只收集一级内联类，也差不多了，谁会在内联类里再写内联类呢？？？
                v.Value.NestedTypes.ToList().ForEach(nt => { ownedTypes[nt.FullName] = nt; });
            });

            foreach (var typeDefinition in hotModule.Types)
            {
                foreach (var methodDefinition in typeDefinition.Methods)
                {
                    if (methodDefinition?.Body?.Instructions == null) continue;
                    foreach (var instruction in methodDefinition.Body.Instructions)
                    {
                        if (instruction.OpCode != OpCodes.Newobj || instruction.Previous == null ||
                            instruction.Previous.OpCode != OpCodes.Ldftn) continue;

                        var method = instruction.Operand as MethodReference;
                        if (method == null) continue;

                        // logger.Log("---find delegate", methodDefinition.FullName, method.FullName);
                        //System.Action/Func类的参数都要收集
                        //除这两种外，能做为参数的方法型变量一定是委托了，检查这个委托是不是定义在hotfix内部的
                        //如果不是，那就要为它生成跨域适配转换器
                        var tr = (TypeReference) method.DeclaringType;
                        var isAction = tr.Name.Contains("Action");
                        var isFunc = tr.Name.Contains("Func");
                        var isConvertor = !ownedTypes.ContainsKey(tr.FullName);

                        // isAction = method.ReturnType.MetadataType == MetadataType.Void;
                        // isFunc = !isAction;
                        if (isAction || isFunc)
                        {
                            if (!(tr is GenericInstanceType))
                            {
                                logger.Log("[delegate action/func register] skip, none generic.", methodDefinition.FullName);
                                continue;
                            }

                            Dictionary<string, string> dic =
                                isAction ? methodDelegates :
                                isFunc? functionDelegates : null;

                            if (dic != null && dic.ContainsKey(tr.FullName))
                                continue;

                            var tstr = isAction? "action": "func";
                            logger.Log($"[delegate {tstr} register]", tr.FullName, tr.DeclaringType);

                            var argsType = "";
                            foreach (var param in ((GenericInstanceType) tr).GenericArguments)
                            {
                                //如果是热更dll自身定义的类型，主工程是不知道的，统统换成ILRuntime.Runtime.Intepreter.ILTypeInstance
                                if (ownedTypes.ContainsKey(param.FullName))
                                {
                                    argsType += typeof(ILRuntime.Runtime.Intepreter.ILTypeInstance).FullName + ",";
                                    logger.Log("  convert private type to ILTypeInstance", param.FullName);
                                }
                                else
                                {
                                    if (param != null)
                                        argsType += param.FullName + ",";
                                }
                            }
                            argsType = argsType.Trim(',');
                            dic.Add(tr.FullName, argsType);

                        }
                        else if (isConvertor)
                        {
                            if (!convertorDelegates.ContainsKey(tr.FullName))
                            {
                                var td = tr.Resolve(); //解决此方法引用，将会加载主工程dll
                                var dm = td.Methods.FirstOrDefault(m => m.Name == "Invoke");
                                if (dm != null)
                                {
                                    logger.Log("[delegate convertor register]", tr.FullName, dm, dm.ContainsGenericParameter, dm.HasGenericParameters);
                                    if (dm.ContainsGenericParameter)
                                    {
                                        dm.Parameters.ToList().ForEach(pd =>
                                        {
                                            logger.Log("pd:", pd, pd.ParameterType.IsGenericParameter);
                                        });
                                        logger.Log("has generic param,skip");
                                        continue;
                                    }
                                    convertorDelegates.Add(tr.FullName, JObject.FromObject(new
                                    {
                                        DelegateName = tr.FullName.Replace("/", "."),
                                            returnType = dm.ReturnType.Name,
                                            argsType = string.Join(",", dm.Parameters.Select(v => v.ParameterType.FullName)),
                                            args = string.Join(",", dm.Parameters.Select(v => v.Name)),
                                    }));
                                    if (dm.Parameters.Count > 0)
                                    {
                                        logger.Log("[delegate action/func register]", dm.FullName);
                                        var argsType = string.Join(",", dm.Parameters.Select(v => v.ParameterType.FullName));
                                        methodDelegates.Add(dm.FullName, argsType);
                                    }
                                }
                            }

                        }
                        else
                        {
                            logger.Log("ignore op", method.DeclaringType);
                        }

                    }
                }
            }

            //开始生成
            var delegateTemplate = Template.Parse(System.IO.File.ReadAllText(templatePath));
            var outputPath = $"{genPath}/RegisterDelegate.cs";
            if (System.IO.Directory.Exists(outputPath))
                System.IO.Directory.Delete(outputPath, true);

            var so = new ScriptObject();
            var context = new TemplateContext();
            context.PushGlobal(so);
            so.Add("methodDelegates", methodDelegates.ToArray());
            so.Add("functionDelegates", functionDelegates.ToArray());
            so.Add("convertorDelegates", convertorDelegates.ToArray().Select(v => v.Value.ToObject<Dictionary<string, object>>()));

            var result = delegateTemplate.Render(context);
            System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath));
            System.IO.File.WriteAllText(outputPath, result);

            Console.WriteLine("** succeed to gen delegate");
        }
    }
}